/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       texture.cpp
 * Author:     karooolek
 * Created on: 2009-03-14
 *
 **********************************************************************************************************************/

#include "texture.h"

// TODO use something else for generating mipmaps
#include <GL/glu.h>

namespace mGameEngine
{
namespace Graphics
{

Texture::Texture() :
    _id(0), _type(TEX_2D), _wrap(TEX_WRAP_CLAMP), _filter(TEX_FILTER_NEAREST), _face(TEX_CUBE_X_POSITIVE)
{
    glGenTextures(1, &_id);
    setImage(NULL);
}

Texture::Texture(const Image *image, TextureWrapping wrap, TextureFiltering filter) :
    _id(0), _type(TEX_2D), _wrap(wrap), _filter(filter), _face(TEX_CUBE_X_POSITIVE)
{
    glGenTextures(1, &_id);
    setImage(image);
}

Texture::Texture(const Image *positiveXimage, const Image *negativeXimage,
                const Image *positiveYimage, const Image *negativeYimage,
                const Image *positiveZimage, const Image *negativeZimage,
                TextureWrapping wrap, TextureFiltering filter) :
    _id(0), _type(TEX_CUBE), _wrap(wrap), _filter(filter), _face(TEX_CUBE_X_POSITIVE)
{
    glGenTextures(1, &_id);
    setActiveFace(TEX_CUBE_X_POSITIVE);
    setImage(positiveXimage);
    setActiveFace(TEX_CUBE_X_NEGATIVE);
    setImage(negativeXimage);
    setActiveFace(TEX_CUBE_Y_POSITIVE);
    setImage(positiveYimage);
    setActiveFace(TEX_CUBE_Y_NEGATIVE);
    setImage(negativeYimage);
    setActiveFace(TEX_CUBE_Z_POSITIVE);
    setImage(positiveZimage);
    setActiveFace(TEX_CUBE_Z_NEGATIVE);
    setImage(negativeZimage);
}

Texture::~Texture()
{
    if(_id)
    {
        glDeleteTextures(1, &_id);
    }
}

void Texture::setImage(const Image *image)
{
    // no image - load white image
    if(!image)
    {
        static const uint8 white[] = {255, 255, 255, 255};
        static const Image whiteImg(white, 1, 1, 32);
        image = &whiteImg;
    }

    // image properties
    uint width = image->getWidth();
    uint height = image->getHeight();
    uint8 bpp = image->getBitsPerPixel();
    const uint8 *data = image->getData();

    // no empty image allowed
    if(!width || !height || !bpp || !data)
    {
        return;
    }

    // select openGL texture type
    uint target;
    switch(_type)
    {
    // one dimensional texture
    case TEX_1D:
        target = GL_TEXTURE_1D;

    // two dimensional texture
    case TEX_2D:
        target = GL_TEXTURE_2D;
        break;

    // cube texture
    case TEX_CUBE:
        target = GL_TEXTURE_CUBE_MAP;
        break;

        // no 3d images
    default:
        return;
    }

    // load image to texture
    glBindTexture(target, _id);
    
    // set new target if cube texture
    if(_type == TEX_CUBE)
    {
        target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + _face;
    }

    switch(bpp)
    {
    // 8 bit - alpha channel
    case 8:
        gluBuild2DMipmaps(target,
                        GL_ALPHA, width, height,
                        GL_ALPHA, GL_UNSIGNED_BYTE,
                        data);
        break;

    // 16 bit - luminance and alpha channel
    case 16:
        gluBuild2DMipmaps(target,
                        GL_LUMINANCE_ALPHA, width, height,
                        GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
                        data);
        break;

    // 24 bit - RGB
    case 24:
        gluBuild2DMipmaps(target,
                        GL_RGB, width, height,
                        GL_RGB, GL_UNSIGNED_BYTE,
                        data);
        break;

    // 32 bit - RGBA
    case 32:
        gluBuild2DMipmaps(target,
                        GL_RGBA, width, height,
                        GL_RGBA, GL_UNSIGNED_BYTE,
                        data);
        break;
    }
}

void Texture::bind(uint8 texUnit) const
{
    // no texture object or invalid texture mapping unit
    if(texUnit >= 32)
    {
        return;
    }

    // select texture mapping unit
    glActiveTexture(GL_TEXTURE0 + texUnit);

    // select openGL texture type
    uint target;
    switch(_type)
    {
    case TEX_1D:
        target = GL_TEXTURE_1D;
        break;
    case TEX_2D:
        target = GL_TEXTURE_2D;
        break;
    case TEX_3D:
        target = GL_TEXTURE_3D;
        break;
    case TEX_CUBE:
        target = GL_TEXTURE_CUBE_MAP;
        break;
    }

    // bind texture
    glBindTexture(target, _id);

    // select wrapping mode
    switch(_wrap)
    {
    case TEX_WRAP_CLAMP:
        glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP);
        break;
    case TEX_WRAP_REPEAT:
        glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
        break;
    case TEX_WRAP_REPEAT_MIRRORED:
        glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
        glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
        break;
    }

    // select filtering mode
    switch(_filter)
    {
    case TEX_FILTER_NEAREST:
        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        break;
    case TEX_FILTER_LINEAR:
        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        break;
    case TEX_FILTER_BILINEAR:
        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        break;
    case TEX_FILTER_TRILINEAR:
        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        break;
    case TEX_FILTER_ANISOTROPIC:
        float maxAnisotropy;
        glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
        glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
        break;
    }
}

}
}

